/*
 * Copyright 2011-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sabayframework.cluster.impl;

import java.util.concurrent.ThreadFactory;

import org.sabayframework.activity.ActivityMonitor;
import org.sabayframework.channel.Channel;
import org.sabayframework.cluster.DistributedActivity;
import org.sabayframework.cluster.NodeExecutionQueue;
import org.sabayframework.queue.impl.ActivityContextImpl;
import org.sabayframework.queue.impl.ArrayDataQueue;
import org.sabayframework.queue.impl.BaseExecutionQueue;
import org.sabayframework.queue.impl.Monitor;

/**
 * Execution queue for a single processor.
 * @author Alex Antonau
 * */
public class ProcessorExecutionQueue extends BaseExecutionQueue implements Runnable, NodeExecutionQueue {

	private final ArrayDataQueue<ActivityContextImpl<?,?>> queue;
	private final Monitor monitor;
	private volatile ActivityContextImpl<?,?> running;
	private final int maxPollLatencyMillis;
	private final int processorNo;
	
	public ProcessorExecutionQueue(int processorNo, ThreadFactory threadFactory, int maxPollLatencyMillis) {
		super(threadFactory);
		queue = new ArrayDataQueue<ActivityContextImpl<?,?>>(1000);
		monitor = new Monitor();
		this.maxPollLatencyMillis = maxPollLatencyMillis;
		this.processorNo = processorNo;
	}

	@Override
	public int size() {
		return queue.size();
	}

	@Override
	public boolean isEmpty() {
		return queue.isEmpty();
	}

	@Override
	public void channelChanged(Channel<?> input) {
		if(running == null)monitor.changed();
	}

	@Override
	protected int getNextWorkerNo() {
		return processorNo;
	}

	@Override
	protected Runnable createWorker(int workerNo) {
		return this;
	}

	@Override
	protected <I, O> void assignToWorker(ActivityContextImpl<I, O> ctx) {
		queue.add(ctx);
		if(running == null)monitor.changed();
	}
	@Override
	public void run() {
		while(active){
			try{
				final ActivityContextImpl<?,?> a = nextReady();
				running=a;
				runActivity(a, false);
				returnActivity(a);
				running=null;
			} 
			catch (InterruptedException e) {}
			catch (Throwable t) {
				setLastError(t);
			}
		}
	}
	private void debriefActivity(ActivityContextImpl<?,?> a){
		a.afterRemoving(this);
	}
	
	private void returnActivity(final ActivityContextImpl<?, ?> a) {
		if(!a.isDone()) queue.add(a);
		else debriefActivity(a);
	}
	private ActivityContextImpl<?, ?> pollQueue(ArrayDataQueue<ActivityContextImpl<?, ?>> queue, int size){
		//loop through the queue and look for ready activities
		while(size-->0){
			final ActivityContextImpl<?,?> a = queue.poll();
			if(a == null) continue;
			if(a.isReady()) {					
				return a;
			}
			if(!a.isDone()) queue.add(a);
			else debriefActivity(a);
		};
		return null;
	}
	
	private ActivityContextImpl<?, ?> nextReady() throws InterruptedException {
		while(active){
			final int qsize = queue.size();
			ActivityContextImpl<?,?> a = pollQueue(queue, qsize);
			if(a!=null) return a;			
			monitor.lock();
			try{
				if(queue.size() == qsize) monitor.waitChanged(maxPollLatencyMillis);
			} finally {
				monitor.unlock();
			}
		}
		throw new InterruptedException();
	}

	@Override
	public <I, O> ActivityMonitor<I, O> submit(DistributedActivity<I, O> da, Channel<I> input, Channel<O> output) {
		return submit(da.getActivity(), input, output);
	}

}
